package ligne2vie.topos.server.service;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.logging.Logger;

import ligne2vie.topos.server.bo.ActionHistory;
import ligne2vie.topos.server.bo.ActionHistory.ActionType;
import ligne2vie.topos.server.bo.EntityState;
import ligne2vie.topos.server.bo.Summit;
import ligne2vie.topos.server.bo.geocell.GeocellManager;
import ligne2vie.topos.server.bo.geocell.Point;
import ligne2vie.topos.shared.bo.TSummit;

import org.junit.After;
import org.junit.Before;

import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
import com.google.appengine.tools.development.testing.LocalUserServiceTestConfig;

/**
 * Convenience class providing common bases to manage summits.
 * 
 * @author thuguerre
 * @since 1.0.0
 */
public class AbstractSummitServiceTestCase {

	private static final Logger LOGGER = Logger.getLogger("ligne2vie");
	private final LocalServiceTestHelper helper = new LocalServiceTestHelper(new LocalUserServiceTestConfig(),
			new LocalDatastoreServiceTestConfig());

	// --- user login information

	protected static final String AUTH_DOMAIN = "test.com";
	protected static final String USER_EMAIL = "junit@test.com";

	// --- summit general information
	
	protected static final int INEXISTENT_SUMMIT_ID = -10;
	
	// --- summit creation information

	protected final String createName = UUID.randomUUID().toString();
	protected final int createAltitude = 10;
	protected final long createLatitude = 10;
	protected final long createLongitude = 10;
	protected final String createDescription = "desc";

	// --- summit edition information

	protected final String editName = UUID.randomUUID().toString();
	protected final int editAltitude = 20;
	protected final long editLatitude = 20;
	protected final long editLongitude = 20;
	protected final String editDescription = "desc2";

	// --- service to test

	protected final SummitServiceImpl service = new SummitServiceImpl();

	@Before
	public void setUp() {

		helper.setUp();

		// --- verifying general information

		assertEquals(0, service.getAllSummits().size());
	}

	@After
	public void tearDown() {
		helper.tearDown();
	}

	/**
	 * Creating and returning the summit result, with creation information defined in class.
	 * 
	 * @return Created summit.
	 */
	protected TSummit createSummit() {

		// --- creating the summit

		LOGGER.fine("Creating summit...");
		TSummit createdSummit = service.createSummit(createName, createAltitude, createLatitude, createLongitude,
				createDescription);
		LOGGER.fine("Summit created.");

		// --- returning summit

		return createdSummit;
	}

	/**
	 * Editing and returning the edited summit, with edition information defined in class.
	 * 
	 * @param summitId
	 *            Identifier of the originally created summit.
	 * @return Edited summit.
	 */
	protected TSummit editSummit(long summitId) {

		// --- editing summit

		LOGGER.fine("Editing summit...");
		TSummit editedSummit = service.editSummit(summitId, editName, editAltitude, editLatitude, editLongitude,
				editDescription);
		LOGGER.fine("Summit edited.");

		// --- returning summit

		return editedSummit;
	}

	/**
	 * Removing the summit whose identifier is given as parameter.
	 */
	protected void removeSummit(long summitId) {

		LOGGER.fine("Removing summit...");
		service.removeSummit(summitId);
		LOGGER.fine("Summit removed.");
	}

	/**
	 * Logs in a test user.
	 * 
	 * @param isAdmin
	 *            <code>true</code> if the user is application admin. <code>false</code> else.
	 */
	protected void userLogin(boolean isAdmin) {

		helper.setEnvAuthDomain(AUTH_DOMAIN);
		helper.setEnvEmail(USER_EMAIL);
		helper.setEnvIsLoggedIn(true);

		helper.setEnvIsAdmin(isAdmin);
	}

	/**
	 * Logs out the test user.
	 */
	protected void userLogout() {
		helper.setEnvIsLoggedIn(false);
	}

	/**
	 * Verify if the given action history is correctly filled.
	 * 
	 * @param actionHistory
	 *            Action history to verify.
	 * @param actionType
	 *            Type of action to verify.
	 */
	protected void verifyActionHistory(ActionHistory actionHistory, ActionType actionType) {

		Date currentDate = new Date();

		assertEquals(actionType, actionHistory.getAction());
		assertEquals(USER_EMAIL, actionHistory.getUserEmail());
		assertTrue(actionHistory.getCreationDate().before(currentDate)
				|| actionHistory.getCreationDate().equals(currentDate));
	}

	/**
	 * Verify if the given list of histories contains all expected and valid actions.
	 * 
	 * @param histories
	 *            List of {@link ActionHistory} to verify.
	 * @param expectedActions
	 *            List of expected actions.
	 */
	protected void verifyActionHistories(List<ActionHistory> histories, List<ActionType> expectedActions) {

		// --- verifying main information

		assertNotNull(histories);
		assertEquals(expectedActions.size(), histories.size());

		// --- browsing all expected actions

		for (ActionType expectedAction : expectedActions) {

			Integer indexHistory = null;

			// --- finding corresponding history

			for (int i = 0; i < histories.size(); i++) {
				if (histories.get(i).getAction().equals(expectedAction)) {
					indexHistory = i;
					break;
				}
			}

			// --- verifying history

			if (indexHistory == null) {

				fail("No history has been found.");

			} else {

				ActionHistory history = histories.get(indexHistory);
				verifyActionHistory(history, expectedAction);
			}

			// --- removing just verified history to guarantee singleton

			histories.remove(indexHistory.intValue());
		}
	}

	/**
	 * Verify if the given summit has all information corresponding to the one defined in class for a created summit.
	 * 
	 * @param createdSummitId
	 *            Identifier of the originally created summit.
	 * @param createdSummit
	 *            Summit to verify. It is supposed to have been just created.
	 */
	protected void verifyCreatedSummit(long createdSummitId, Summit createdSummit) {

		// --- verifying main information

		assertNotNull(createdSummit);
		assertEquals(createdSummitId, createdSummit.getId().getId());
		assertEquals(createName, createdSummit.getName());
		assertEquals(createAltitude, createdSummit.getAltitude());
		assertEquals(createLatitude, createdSummit.getLatitude(), 0);
		assertEquals(createLongitude, createdSummit.getLongitude(), 0);
		assertEquals(createDescription, createdSummit.getDescription());
		assertEquals(EntityState.TO_VERIFY, createdSummit.getState());

		// --- verifying action histories

		List<ActionType> expectedActions = new ArrayList<ActionType>();
		expectedActions.add(ActionType.SUMMIT_CREATE);

		verifyActionHistories(createdSummit.getHistories(), expectedActions);

		// --- verify geo cells

		List<String> expectedGeoCells = GeocellManager.generateGeoCell(new Point(createLatitude, createLongitude));
		verifyGeoCells(createdSummit.getGeocells(), expectedGeoCells);
	}

	/**
	 * Verify if the given summit has all information corresponding to the one defined in class for a created summit.
	 * 
	 * @param createdSummit
	 *            Summit to verify. It is supposed to have just been created.
	 */
	protected void verifyCreatedTSummit(TSummit createdSummit) {

		assertNotNull(createdSummit);
		assertTrue(createdSummit.getId() > 0);
		assertEquals(createName, createdSummit.getName());
		assertEquals(createAltitude, createdSummit.getAltitude());
		assertEquals(createLatitude, createdSummit.getLatitude(), 0);
		assertEquals(createLongitude, createdSummit.getLongitude(), 0);
		assertEquals(createDescription, createdSummit.getDescription());
	}

	/**
	 * Verify if the given summit has all information corresponding to the one defined in class for an edited summit.
	 * 
	 * @param createdSummitId
	 *            Identifier of the originally created summit.
	 * @param editedSummit
	 *            Summit to verify. It is supposed to have just been edited.
	 */
	protected void verifyEditedSummit(long createdSummitId, Summit editedSummit) {

		// --- verifying main information

		assertNotNull(editedSummit);
		assertEquals(createdSummitId, editedSummit.getId().getId());
		assertEquals(editName, editedSummit.getName());
		assertEquals(editAltitude, editedSummit.getAltitude());
		assertEquals(editLatitude, editedSummit.getLatitude(), 0);
		assertEquals(editLongitude, editedSummit.getLongitude(), 0);
		assertEquals(editDescription, editedSummit.getDescription());
		assertEquals(EntityState.TO_VERIFY, editedSummit.getState());

		// --- verifying histories

		List<ActionType> expectedActions = new ArrayList<ActionType>();
		expectedActions.add(ActionType.SUMMIT_CREATE);
		expectedActions.add(ActionType.SUMMIT_EDIT);

		verifyActionHistories(editedSummit.getHistories(), expectedActions);
		
		// --- verify geo cells

		List<String> expectedGeoCells = GeocellManager.generateGeoCell(new Point(editLatitude, editLongitude));
		verifyGeoCells(editedSummit.getGeocells(), expectedGeoCells);
	}

	/**
	 * Verify if the given summit has all information corresponding to the one defined in class for an edited summit.
	 * 
	 * @param createdSummitId
	 *            Identifier of the originally created summit.
	 * @param editedSummit
	 *            Summit to verify. It is supposed to have just been edited.
	 */
	protected void verifyEditedTSummit(long createdSummitId, TSummit editedSummit) {

		assertNotNull(editedSummit);
		assertEquals(createdSummitId, editedSummit.getId());
		assertEquals(editName, editedSummit.getName());
		assertEquals(editAltitude, editedSummit.getAltitude());
		assertEquals(editLatitude, editedSummit.getLatitude(), 0);
		assertEquals(editLongitude, editedSummit.getLongitude(), 0);
		assertEquals(editDescription, editedSummit.getDescription());
	}
	
	/**
	 * Verify if the given list of geocells contains all expected and valid geocells.
	 * 
	 * @param geocells
	 *            List of {@link String} to verify.
	 * @param expectedGeocells
	 *            List of expected geocells.
	 */
	protected void verifyGeoCells(List<String> geocells, List<String> expectedGeocells) {

		// --- verifying main information

		assertNotNull(geocells);
		assertEquals(expectedGeocells.size(), geocells.size());

		assertTrue(geocells.containsAll(expectedGeocells));
		assertTrue(expectedGeocells.containsAll(geocells));
	}

	/**
	 * Verify if the given summit has all information corresponding to a valid removed summit.
	 * 
	 * @param createdSummitId
	 *            Identifier of the originally created summit.
	 * @param removedSummit
	 *            Summit to verify. It is supposed to have just been removed.
	 * @param hasBeenEdited
	 *            <code>true</code> if the summit should have edition information. <code>false</code> if it should have
	 *            creation information.
	 */
	protected void verifyRemovedSummit(long createdSummitId, Summit removedSummit, boolean hasBeenEdited) {

		assertNotNull(removedSummit);
		assertEquals(createdSummitId, removedSummit.getId().getId());
		assertEquals(EntityState.REMOVED, removedSummit.getState());

		if (hasBeenEdited) {

			assertEquals(editName, removedSummit.getName());
			assertEquals(editAltitude, removedSummit.getAltitude());
			assertEquals(editLatitude, removedSummit.getLatitude(), 0);
			assertEquals(editLongitude, removedSummit.getLongitude(), 0);
			assertEquals(editDescription, removedSummit.getDescription());

		} else {

			assertEquals(createName, removedSummit.getName());
			assertEquals(createAltitude, removedSummit.getAltitude());
			assertEquals(createLatitude, removedSummit.getLatitude(), 0);
			assertEquals(createLongitude, removedSummit.getLongitude(), 0);
			assertEquals(createDescription, removedSummit.getDescription());
		}

		// --- verifying histories

		List<ActionType> expectedActions = new ArrayList<ActionType>();
		expectedActions.add(ActionType.SUMMIT_CREATE);
		if (hasBeenEdited) {
			expectedActions.add(ActionType.SUMMIT_EDIT);
		}
		expectedActions.add(ActionType.SUMMIT_REMOVE);

		verifyActionHistories(removedSummit.getHistories(), expectedActions);
	}
}
